package cc.momas.v2;

import java.io.CharArrayReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * @author Sod-Momas
 * @since 2020/11/22
 */
public class Lexer implements Runnable {
    private final List<Token> tokenList = new ArrayList<>();
    private StringBuilder curTokenText;
    private SimpleToken curToken;
    private final String script;
    private final Logger logger = Logger.getLogger(Lexer.class.getName());

    public Lexer(String script) {
        this.script = script;
    }

    @Override
    public void run() {
        info("开始解析脚本：【", script, "】");
        tokenize(script);
        dump();
    }

    private void tokenize(String script) {
        final CharArrayReader reader = new CharArrayReader(script.toCharArray());
        curToken = new SimpleToken();
        curTokenText = new StringBuilder();
        DfaState state = DfaState.INITIAL;
        try {
            char cur;
            while ((cur = (char) reader.read()) != '\uFFFF') {
                debug("当前字符：", String.valueOf(cur));
                switch (state) {
                    case INITIAL -> state = initToken(cur);
                    case IDENTIFIER -> {
                        if (isAlpha(cur) || isDigit(cur)) {
                            curTokenText.append(cur);
                            break;
                        }
                        state = initToken(cur);
                    }
                    case ASSIGNMENT -> {
                        if ('=' == cur) {
                            curTokenText.append(cur);
                            state = DfaState.EQUAL;
                            break;
                        }
                        state = initToken(cur);
                    }
                    case LESS_THAN -> {
                        if (cur == '=') {
                            state = DfaState.INITIAL;
                            curTokenText.append(cur);
                            curToken.tokenType = TokenType.LESS_EQUAL_THAN;
                            break;
                        }
                        state = initToken(cur);
                    }
                    case GREAT_THAN -> {
                        if (cur == '=') {
                            state = DfaState.INITIAL;
                            curTokenText.append(cur);
                            curToken.tokenType = TokenType.GREAT_EQUAL_THAN;
                            break;
                        }
                        state = initToken(cur);
                    }
//                    case IF1 -> {
//                        if ('f' == cur) {
//                            curTokenText.append(cur);
//                            state = DfaState.IF;
//                            break;
//                        }
//                        state = initToken(cur);
//                    }
                    case PLUS,
                            MINUS,
                            STAR,
                            SLASH,
                            GREAT_EQUAL_THAN,
                            LESS_EQUAL_THAN,
                            RIGHT_PAREN,
                            SEMICOLON -> {
                        state = initToken(cur);
                    }
                    case INT1 -> {
                        if ('n' == cur) {
                            curTokenText.append(cur);
                            state = DfaState.INT2;
                            break;
                        }
                        if (isDigit(cur) || isAlpha(cur)) {
                            curTokenText.append(cur);
                            state = DfaState.IDENTIFIER;
                        }
                        state = initToken(cur);
                    }
                    case INT2 -> {
                        if ('t' == cur) {
                            curTokenText.append(cur);
                            state = DfaState.INT3;
                            break;
                        }
                        if (isDigit(cur) || isAlpha(cur)) {
                            curTokenText.append(cur);
                            state = DfaState.IDENTIFIER;
                            break;
                        }
                        state = initToken(cur);
                    }
                    case INT3 -> {
                        // 是字母或数字，表示它是个int开头的标识符
                        if (isAlpha(cur) || isDigit(cur)) {
                            curTokenText.append(cur);
                            state = DfaState.IDENTIFIER;
                            break;
                        }
                        curToken.tokenType = TokenType.INT;
                        state = DfaState.INITIAL;
                    }
                    case INT_LITERAL -> {
                        if (isDigit(cur)) {
                            curTokenText.append(cur);
                            break;
                        }
                        // 如果不是数字的话说明数字字面量到结尾了
                        state = initToken(cur);
                    }
                    default -> {
                        debug("不认识的字符：", String.valueOf(cur));
                    }
                }
            }
            // 保存最后一个token
            if (0 < curTokenText.length()) {
//                curToken.tokenText = curTokenText.toString();
//                tokenList.add(curToken);
                initToken(cur);
            }
            curToken = null;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    private DfaState initToken(char cur) {
        if (0 < curTokenText.length()) {
            // 已经有值，说明需要把本次解析出来的token装进容器里了
            curToken.tokenText = curTokenText.toString();
            tokenList.add(curToken);

            // 存储一个新token
            curTokenText = new StringBuilder();
            curToken = new SimpleToken();
        }

        // token的第一个字符
        switch (cur) {
            case '+' -> {
                curTokenText.append(cur);
                curToken.tokenType = TokenType.PLUS;
                return DfaState.PLUS;
            }
            case '-' -> {
                curTokenText.append(cur);
                curToken.tokenType = TokenType.MINUS;
                return DfaState.MINUS;
            }
            case '*' -> {
                curTokenText.append(cur);
                curToken.tokenType = TokenType.STAR;
                return DfaState.STAR;
            }
            case '/' -> {
                curTokenText.append(cur);
                curToken.tokenType = TokenType.SLASH;
                return DfaState.SLASH;
            }
            case ';' -> {
                curTokenText.append(cur);
                curToken.tokenType = TokenType.SEMICOLON;
                return DfaState.SEMICOLON;
            }
            case '<' -> {
                curTokenText.append(cur);
                curToken.tokenType = TokenType.LESS_THAN;
                return DfaState.LESS_THAN;
            }
            case '>' -> {
                curTokenText.append(cur);
                curToken.tokenType = TokenType.GREAT_THAN;
                return DfaState.GREAT_THAN;
            }
            case '(' -> {
                curTokenText.append(cur);
                curToken.tokenType = TokenType.LEFT_PAREN;
                return DfaState.LEFT_PAREN;
            }
            case ')' -> {
                curTokenText.append(cur);
                curToken.tokenType = TokenType.RIGHT_PAREN;
                return DfaState.RIGHT_PAREN;
            }
            case '=' -> {
                curTokenText.append(cur);
                curToken.tokenType = TokenType.ASSIGNMENT;
                return DfaState.ASSIGNMENT;
            }
            case '\"' -> {
                curTokenText.append(cur);
                curToken.tokenType = TokenType.STRING_LITERAL;
                return DfaState.STRING_LITERAL;
            }
            default -> {
                if (isAlpha(cur)) {
                    curTokenText.append(cur);
                    curToken.tokenType = TokenType.IDENTIFIER;
                    // 初步判断关键字 int
                    // 判定进入自动机状态，而tokenType还是标识符
                    if (cur == 'i') {
                        return DfaState.INT1;
                    }
                    return DfaState.IDENTIFIER;
                }
                if (isDigit(cur)) {
                    curTokenText.append(cur);
                    curToken.tokenType = TokenType.INT_LITERAL;
                    return DfaState.INT_LITERAL;
                }
                // 不认识的情况，返回一个新的状态
                return DfaState.INITIAL;
            }
        }
    }


    //是否是字母
    private boolean isAlpha(int ch) {
        return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z';
    }

    //是否是数字
    private boolean isDigit(int ch) {
        return ch >= '0' && ch <= '9';
    }

    //是否是空白字符
    private boolean isBlank(int ch) {
        return ch == ' ' || ch == '\t' || ch == '\n';
    }

    /**
     * 打印日志到控制台
     *
     * @param log 日志内容
     */
    private void info(CharSequence... log) {
//        logger.info("[" + Thread.currentThread().getName() + "]" + String.join("", log));
        System.out.println(String.join("", log));
    }


    private void debug(CharSequence... log) {
        logger.fine(String.join("", log));
    }

    /**
     * 输出token列表
     */
    private void dump() {
        final StringBuilder sb = new StringBuilder();
        for (Token token : tokenList) {
            final TokenType type = token.getTokenType();
            final String text = token.getTokenText();
            sb.append(String.format("%s\t\t%s%n", text, type));
        }
        info(sb.toString());
    }
}
